package dns

import (
	"fmt"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/catroll/fastdns/internal/logger"
)

// CacheKey 表示缓存键
type CacheKey struct {
	Domain     string
	RecordType RecordType
}

// String 返回缓存键的字符串表示
func (k CacheKey) String() string {
	return fmt.Sprintf("%s:%s", k.Domain, k.RecordType)
}

// CacheEntry 表示缓存中的一个条目
type CacheEntry struct {
	Records  []any     // 用于所有类型的记录 (包括 A/AAAA 记录的 net.IP)
	ExpireAt time.Time // 过期时间
}

// Cache 实现了一个简单的 DNS 缓存
type Cache struct {
	entries  map[string]CacheEntry // 使用字符串格式的复合键
	capacity int
	ttl      time.Duration
	mu       sync.RWMutex
	hits     int
	misses   int
}

// NewCache 创建一个新的 DNS 缓存
func NewCache(capacity int, ttl time.Duration) *Cache {
	c := &Cache{
		entries:  make(map[string]CacheEntry, capacity),
		capacity: capacity,
		ttl:      ttl,
	}

	// 启动定期清理过期条目的协程
	go c.startCleanupRoutine()

	return c
}

// 标准化域名作为缓存键
func normalizeKey(domain string, recordType RecordType) string {
	// 转换为小写
	domain = strings.ToLower(domain)

	// 移除末尾的点
	domain = strings.TrimSuffix(domain, ".")

	// 创建复合键
	key := CacheKey{
		Domain:     domain,
		RecordType: recordType,
	}

	return key.String()
}

// GetIPs 从缓存中获取域名对应的 IP 地址
func (c *Cache) GetIPs(domain string, recordType RecordType) ([]net.IP, bool) {
	// 先获取记录
	records, found := c.GetRecords(domain, recordType)
	if !found {
		return nil, false
	}

	// 将记录转换为 IP 地址
	ips := make([]net.IP, 0, len(records))
	for _, record := range records {
		if ip, ok := record.(net.IP); ok {
			ips = append(ips, ip)
		}
	}

	logger.Debug("IP 缓存命中", "domain", domain, "type", recordType, "ips", ips)
	return ips, len(ips) > 0
}

// GetRecords 从缓存中获取域名对应的记录
func (c *Cache) GetRecords(domain string, recordType RecordType) ([]any, bool) {
	// 标准化域名和创建复合键
	key := normalizeKey(domain, recordType)
	logger.Debug("查询记录缓存", "domain", domain, "type", recordType, "key", key)

	c.mu.RLock()
	entry, found := c.entries[key]
	c.mu.RUnlock()

	if !found {
		c.mu.Lock()
		c.misses++
		c.mu.Unlock()
		logger.Debug("记录缓存未命中", "domain", domain, "type", recordType, "key", key)
		return nil, false
	}

	// 检查条目是否过期
	if time.Now().After(entry.ExpireAt) {
		c.mu.Lock()
		logger.Debug("DNS 缓存过期", "domain", domain, "type", recordType, "key", key)
		delete(c.entries, key)
		c.misses++
		c.mu.Unlock()
		return nil, false
	}

	c.mu.Lock()
	c.hits++
	c.mu.Unlock()
	logger.Debug("记录缓存命中", "domain", domain, "type", recordType, "records", len(entry.Records))
	return entry.Records, true
}

// SetIPs 将域名和 IP 地址添加到缓存中
func (c *Cache) SetIPs(domain string, recordType RecordType, ips []net.IP) {
	// 将 IP 地址转换为通用记录格式
	records := make([]any, len(ips))
	for i, ip := range ips {
		records[i] = ip
	}

	// 调用通用的 SetRecords 方法
	c.SetRecords(domain, recordType, records)
	logger.Debug("DNS IP 缓存更新", "domain", domain, "type", recordType, "ips", ips)
}

// SetRecords 将域名和记录添加到缓存中
func (c *Cache) SetRecords(domain string, recordType RecordType, records []any) {
	// 标准化域名和创建复合键
	key := normalizeKey(domain, recordType)
	logger.Debug("添加记录缓存", "domain", domain, "type", recordType, "key", key, "records", len(records))

	c.mu.Lock()
	defer c.mu.Unlock()

	// 如果缓存已满，清理一些条目
	if len(c.entries) >= c.capacity {
		c.evictOldest()
	}

	// 添加新条目
	logger.Debug("DNS 记录缓存更新", "domain", domain, "type", recordType, "key", key, "records", len(records))
	c.entries[key] = CacheEntry{
		Records:  records,
		ExpireAt: time.Now().Add(c.ttl),
	}
}

// evictOldest 清理最旧的缓存条目
func (c *Cache) evictOldest() {
	var oldestDomain string
	var oldestTime time.Time

	// 初始化为最大时间
	oldestTime = time.Now().Add(100 * 365 * 24 * time.Hour) // 约 100 年后

	// 查找最旧的条目
	for domain, entry := range c.entries {
		if entry.ExpireAt.Before(oldestTime) {
			oldestDomain = domain
			oldestTime = entry.ExpireAt
		}
	}

	// 删除最旧的条目
	if oldestDomain != "" {
		logger.Debug("缓存清理：移除最旧条目", "domain", oldestDomain)
		delete(c.entries, oldestDomain)
	}
}

// Clear 清空缓存
func (c *Cache) Clear() {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.entries = make(map[string]CacheEntry, c.capacity)
	c.hits = 0
	c.misses = 0
}

// Size 返回当前缓存中的条目数量
func (c *Cache) Size() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return len(c.entries)
}

// Capacity 返回缓存的最大容量
func (c *Cache) Capacity() int {
	return c.capacity
}

// HitRatio 返回缓存命中率
func (c *Cache) HitRatio() float64 {
	c.mu.RLock()
	defer c.mu.RUnlock()

	total := c.hits + c.misses
	if total == 0 {
		return 0.0
	}
	return float64(c.hits) / float64(total)
}

// startCleanupRoutine 启动定期清理过期条目的协程
func (c *Cache) startCleanupRoutine() {
	ticker := time.NewTicker(c.ttl / 2) // 每半个 TTL 清理一次
	defer ticker.Stop()

	for range ticker.C {
		logger.Debug("缓存清理：清理过期条目")
		c.cleanupExpired()
	}
}

// cleanupExpired 清理所有过期的缓存条目
func (c *Cache) cleanupExpired() {
	now := time.Now()

	c.mu.Lock()
	defer c.mu.Unlock()

	// 找出所有过期的条目
	expiredDomains := make([]string, 0)
	for domain, entry := range c.entries {
		if now.After(entry.ExpireAt) {
			expiredDomains = append(expiredDomains, domain)
		}
	}

	// 删除过期条目
	for _, domain := range expiredDomains {
		logger.Debug("缓存清理：移除过期条目", "domain", domain)
		delete(c.entries, domain)
	}

	if len(expiredDomains) > 0 {
		logger.Debug("缓存清理：移除过期条目", "count", len(expiredDomains))
	}
}

// GetStats 获取缓存统计信息
func (c *Cache) GetStats() (size int, capacity int, hits int, misses int, hitRatio float64) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	size = len(c.entries)
	capacity = c.capacity
	hits = c.hits
	misses = c.misses

	total := hits + misses
	if total == 0 {
		hitRatio = 0.0
	} else {
		hitRatio = float64(hits) / float64(total)
	}

	return
}
